﻿using System;
using System.Text;
using System.Globalization;
using System.Diagnostics;

namespace Gadgeteer.Modules.Community.XBee
{
    /// <summary>
    /// This class is used to build outgoing messages
    /// </summary>
    public static class APIMessageBuilder
    {
        /// <summary>
        /// Builds a message that will be sent to all nodes
        /// </summary>
        /// <param name="data">The payload to send</param>
        /// <param name="correlationId">Returned by TransmitStatus notification, used for correlation</param>
        /// <returns>a byte array to send over serial line</returns>
        public static byte[] BuildBroadcastTransmitRequest(string data, ushort correlationId)
        {
            return BuildBroadcastTransmitRequest(UTF8Encoding.UTF8.GetBytes(data), correlationId);
        }

        /// <summary>
        /// Builds a byte array for a broadcast transmit request
        /// </summary>
        /// <param name="data">Payload of message</param>
        /// <param name="correlationId">Returned by TransmitStatus notification, used for correlation</param>
        /// <returns>a byte array to send over serial line</returns>
        public static byte[] BuildBroadcastTransmitRequest(byte[] data, ushort correlationId)
        {
            int numBytes = 18 + data.Length;
            byte[] message = new byte[numBytes];
            message[0] = (byte)APIMessageEnum.StartDelimiter;

            message[1] = 0x00;
            message[2] = (byte)(message.Length - 4);

            message[3] = (byte)APIMessageEnum.TransmitRequest;
            message[4] = (byte)correlationId;

            message[5] = 0x00;
            message[6] = 0x00;
            message[7] = 0x00;
            message[8] = 0x00;

            message[9] = 0x00;
            message[10] = 0x00;
            message[11] = 0xFF;
            message[12] = 0xFF;

            message[13] = 0xFF;
            message[14] = 0xFE;

            message[15] = 0x00; // broadcast radius

            message[16] = 0x00; // options

            Array.Copy(data, 0, message, 17, data.Length);

            message[message.Length - 1] = (byte)CalculateChecksum(message);

            return message;
        }

        /// <summary>
        /// Builds a message that will be sent to a single node
        /// </summary>
        /// <param name="nodeDescription">The node to send the message to</param>
        /// <param name="data">The payload to send</param>
        /// <param name="correlationId">Returned by TransmitStatus notification, used for correlation</param>
        /// <returns>a byte array to send over serial line</returns>
        public static byte[] BuildTransmitRequest(NodeDescription nodeDescription, string data, ushort correlationId)
        {
            return BuildTransmitRequest(nodeDescription, UTF8Encoding.UTF8.GetBytes(data), correlationId);
        }

        /// <summary>
        /// Builds a message that will be sent to a single node
        /// </summary>
        /// <param name="nodeDescription">The node to send the message to</param>
        /// <param name="data">The payload to send</param>
        /// <param name="correlationId">Returned by TransmitStatus notification, used for correlation</param>
        /// <returns>a byte array to send over serial line</returns>
        public static byte[] BuildTransmitRequest(NodeDescription nodeDescription, byte[] data, ushort correlationId)
        {

            int numBytes = 18 + data.Length;
            byte[] message = new byte[numBytes];
            message[0] = (byte)APIMessageEnum.StartDelimiter;
            
            message[1] = 0x00;
            message[2] = (byte)(message.Length - 4);

            message[3] = (byte)APIMessageEnum.TransmitRequest;
            message[4] = (byte)correlationId;

            byte[] serialNumberHigh = Converter.ToByteArray(nodeDescription.SerialNumberHigh);
            for (int i = 0; i < 4; i++)
            {
                message[i + 5] = serialNumberHigh[i]; 
            }

            byte[] serialNumberLow = Converter.ToByteArray(nodeDescription.SerialNumberLow);
            for (int i = 0; i < 4; i++)
            {
                message[i + 9] = serialNumberLow[i];
            }

            byte[] myAddress = Converter.ToByteArray(nodeDescription.MyAddress);

            for (int i = 0; i < 2; i++)
            {
                message[i + 13] = myAddress[i];
            }

            message[15] = 0x00; // broadcast radius

            message[16] = 0x00; // options

            Array.Copy(data, 0, message, 17, data.Length);

            message[message.Length - 1] = (byte)CalculateChecksum(message);

            return message;
        }

        /// <summary>
        /// Builds an XBee AT Command
        /// </summary>
        /// <param name="atCommand">The XBee AT command, has to be 2 chars</param>
        /// <param name="correlationId">Returned by TransmitStatus notification, used for correlation</param>
        /// <returns>a byte array to send over serial line</returns>
        public static byte[] BuildATCommand(string atCommand, ushort correlationId)
        {
            byte[] message = new byte[8];

            message[0] = (byte)APIMessageEnum.StartDelimiter;

            message[1] = 0x00; // MSB
            message[2] = 0x04; // LSB - number of bytes between length and checksum

            message[3] = (byte)APIMessageEnum.ATCommand; // AT Command
            message[4] = (byte)correlationId; // Frame ID

            message[5] = (byte)atCommand[0];
            message[6] = (byte)atCommand[1];

            message[7] = (byte)CalculateChecksum(message);

            return message;
        }

        /// <summary>
        /// This is used by tests for generating fake transmit status
        /// </summary>
        /// <param name="deliveryStatus">the tests need to vary the delivery status to test error conditions</param>
        /// <returns>message</returns>
        public static byte[] BuildTransmitStatus(byte deliveryStatus)
        {
            byte[] message = new byte[11];

            message[0] = (byte)APIMessageEnum.StartDelimiter;

            message[1] = 0x00; // MSB
            message[2] = 0x07; // LSB - number of bytes between length and checksum

            message[3] = (byte)APIMessageEnum.TransmitStatus;
            message[4] = (byte)0x01; // Frame ID

            message[5] = 0x8D; // in the real world this is the 16 bit network address the packet was delivered to
            message[6] = 0x84; // the rest of the 16 bit address. if unsuccessful this would match the address in TransmitRequest frame TODO: vary this for tests

            message[7] = 0x00; // transmit retry count

            message[8] = deliveryStatus;

            message[9] = 0x01; // discovery status
            message[10] = (byte)CalculateChecksum(message);

            return message;
        }

        private static ushort CalculateChecksum(byte[] message)
        {
            int sum = 0;
            int startIndex = 3; // checksum is calculated from the FrameType up to the checksum
            for (int i = startIndex; i < message.Length - 1; i++)
            {
                sum += (ushort)message[i];
            }
            int lower8Bits = 0xFF & sum;

            int checksum = 0xFF - lower8Bits;
            return (ushort)checksum;
        }
    
    }
}
